#pragma once
#include "model.h"
#include "mesh.h"
#include "vector3.h"
#include "vector2.h"
#include "string_util.h"
#include "vertex.h"
#include "texture_software.h"
#include <string>
#include <vector>
#include <numeric>
#include <cassert>
#include <ostream>

namespace krryn{
namespace data{
	template<typename _RenderTag, typename _Vertex = scene::vertex>
	class obj_loader{
	public:
		typedef scene::index_buffer<_RenderTag> ib_type;
		typedef typename scene::index_buffer<_RenderTag>::ptr ib_ptr;

		typedef scene::vertex_buffer<_RenderTag, _Vertex> vb_type;
		typedef typename scene::vertex_buffer<_RenderTag, _Vertex>::ptr vb_ptr;

		typedef system::renderer<_RenderTag> render_type;

		typedef typename scene::mesh<_RenderTag, _Vertex>::ptr mesh_ptr;
		typedef typename scene::mesh<_RenderTag, _Vertex> mesh_type;

		obj_loader(const std::string &a_Filename) : m_Filename(a_Filename) {}

		scene::model<_RenderTag, _Vertex> load(render_type &a_Renderer){
			std::ifstream f(m_Filename.c_str());
			std::ifstream l(m_Filename.c_str());

			std::vector<std::string> lines;

			std::vector<unsigned int> l_NumFacesByMesh(0);

			unsigned int 
				l_NumFaces = 0,
				l_NumUVs = 0, 
				l_NumVertices = 0,
				l_NumMeshes = 0,
				l_CurrentMesh = -1,
				l_CurrentIndex = 0;

			while(f)
			{
				std::string buffer;
				std::getline(f, buffer);
				lines.push_back(buffer);

				     if (starts_with(buffer, "v ")) l_NumVertices++;
				else if (starts_with(buffer, "vt")) l_NumUVs++;
				else if (starts_with(buffer, "f "))
				{
					l_NumFaces++;

					if(l_NumFacesByMesh.empty()) l_NumFacesByMesh.push_back(0);

					l_NumFacesByMesh[l_NumFacesByMesh.size() - 1]++;
				}
				else if (starts_with(buffer, "g "))
				{
					l_NumMeshes++;
					l_NumFacesByMesh.push_back(0);
				}
			}

			assert(l_NumFaces == std::accumulate(l_NumFacesByMesh.begin(), l_NumFacesByMesh.end(), 0));

			std::cout
				<< "Loading [" << m_Filename << "]\n"
				<< " - Vertices: " << l_NumVertices << "\n"
				<< " - UVs: " << l_NumUVs << "\n"
				<< " - Faces: " << l_NumFaces << "\n";

			std::vector<vector3f> verts;
			std::vector<uv> uvs;

			scene::model<_RenderTag, _Vertex> m;
			m.m_Meshes.resize(l_NumMeshes ? l_NumMeshes : 1);

			float s = 0.1f;

			while(l)
			{
				std::string line;
				std::getline(l, line);
				const char *cline = line.c_str();

				if(starts_with(line, "v "))
				{
					float x, y, z;
					sscanf_s(cline + 2, "%f %f %f", &x, &y, &z);
					verts.push_back(vector3f(x * s, y * s, z * s));
				}
				else if(starts_with(line, "vt"))
				{
					// uv
					float u, v;
					sscanf_s(cline + 3, "%f %f", &u, &v);
					uvs.push_back(uv(u, v));
				}
				else if(starts_with(line, "mtllib"))
				{
					load_mtl(cline + strlen("mtllib") + 1);
				}
				else if(starts_with(line, "usemtl"))
				{
					std::string tmp = core::trim(cline + strlen("usemtl") + 1);
					m.m_Meshes[l_CurrentMesh]->effects().push_back(m_Textures[tmp]);
				}
				else if(starts_with(line, "g "))
				{
					init_group(a_Renderer, l_CurrentMesh, l_CurrentIndex, l_NumFacesByMesh, m);
				}
				else if(starts_with(line, "f "))
				{
					if(l_CurrentMesh == -1)
						init_group(a_Renderer, l_CurrentMesh, l_CurrentIndex, l_NumFacesByMesh, m);

					// face
					unsigned int vnr[9];

					memset(vnr, -1, 9 * sizeof(unsigned int));

					// vertex, uv, normal
					unsigned int vars = sscanf_s(cline + 2, "%i/%i/%i %i/%i/%i %i/%i/%i", 
						&vnr[0], &vnr[1], &vnr[2], &vnr[3], &vnr[4], &vnr[5], &vnr[6], &vnr[7], &vnr[8]);

					if (vars < 9) 
					{
						memset(vnr, -1, 9 * sizeof(unsigned int));

						// vertex, uv
						vars = sscanf_s(cline + 2, "%i/%i %i/%i %i/%i", 
							&vnr[0], &vnr[1], &vnr[3], &vnr[4], &vnr[6], &vnr[7]);
					}

					vb_ptr vb = m.m_Meshes[l_CurrentMesh]->get_vertex_buffer();
					ib_ptr ib = m.m_Meshes[l_CurrentMesh]->get_index_buffer();

					*vb += scene::vertex(verts[vnr[0 * 3] - 1]).uv(uvs[vnr[0 * 3 + 1] - 1]);
					*vb += scene::vertex(verts[vnr[1 * 3] - 1]).uv(uvs[vnr[1 * 3 + 1] - 1]);
					*vb += scene::vertex(verts[vnr[2 * 3] - 1]).uv(uvs[vnr[2 * 3 + 1] - 1]);

					for(int i = 0; i < 3; i++)
						*ib += l_CurrentIndex++;
				}
			}

			// unlock buffers
			for(size_t i = 0; i < m.m_Meshes.size(); i++){
				m.m_Meshes[i]->get_index_buffer()->unlock();
				m.m_Meshes[i]->get_vertex_buffer()->unlock();
			}

			return m;
		}

		vb_ptr create_vertex_buffer(render_type &a_Renderer, unsigned int a_NumVertices){
			vb_ptr vb(new vb_type(a_Renderer, a_NumVertices));

			return vb;
		}

		ib_ptr create_index_buffer(render_type &a_Renderer, unsigned int a_NumIndices){
			ib_ptr ib(new ib_type(a_Renderer, a_NumIndices));

			return ib;
		}
	private:
		void init_group(
			render_type &a_Renderer, 
			unsigned int &l_CurrentMesh, 
			unsigned int &l_CurrentIndex, 
			std::vector<unsigned int> &l_NumFacesByMesh, 
			scene::model<_RenderTag, _Vertex> &m)
		{
			// group
			l_CurrentMesh++;

			m.m_Meshes[l_CurrentMesh] = new mesh_type(
				new vb_type(a_Renderer, 3 * l_NumFacesByMesh[l_CurrentMesh]),
				new ib_type(a_Renderer, 3 * l_NumFacesByMesh[l_CurrentMesh])
			);

			l_CurrentIndex = 0;

			m.m_Meshes[l_CurrentMesh]->get_index_buffer()->lock(scene::write);
			m.m_Meshes[l_CurrentMesh]->get_vertex_buffer()->lock(scene::write);
		}

		void load_mtl(const std::string &a_Filename){
			std::vector<std::string> path = explode(m_Filename, "/");
			path[path.size() - 1] = a_Filename;
			std::string name = trim(trim(trim(implode(path, "/"), '\n'), '\r'));

			printf("%s\n", name.c_str());

			std::ifstream f(name.c_str());

			std::string currentmtl;

			while(f)
			{
				std::string buffer;
				std::getline(f, buffer);
				buffer = trim(trim(buffer, '\t'));
				const char *cline = buffer.c_str();

				if(starts_with(buffer, "newmtl"))
				{
					//sscanf_s(cline + strlen("newtml"), "%s", currentmtl);
					currentmtl = explode(buffer, " ")[1];
				}
				else if(starts_with(buffer, "map_Kd"))
				{
					std::string tex = explode(buffer, " ")[1];

					try
					{
						canvas *c = new canvas(tex);
						c->vflip();

						texture_software *t = new texture_software(c, 0);
						t->set_name(tex);

						m_Textures[currentmtl] = t;
					}catch(...)
					{
						printf("Texture not found %s\n", tex.c_str());
					}
				}
			}
		}

		std::map<std::string, system_imp::texture_software*> m_Textures;
		std::string m_Filename;
	};
}
}